# app.py
import subprocess
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from tkinter import font
import os
import datetime
from utils.preprocessor import LogPreprocessor
from utils.analyzer import LogAnalyzer
from pathlib import Path
import threading
from queue import Queue

class App(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)  # 正确初始化父类
        default_font = font.nametofont("TkDefaultFont")
        default_font.configure(size=20)
        self.title("日志分析系统")
        self.geometry("1200x700")
        self.end_date=None
        self.start_date=None
        self.task_queue = Queue()
        self.is_processing = False
        self.current_task = None
        # 选择文件和结果显示
        self.page1 = tk.Frame(self, bg='lightblue')
        self.page2 = tk.Frame(self, bg='lightgreen')

  
 
        # 调整 page1 的大小和位置
        self.page1.place(x=20, y=50, width=300, height=600)  # 调整为更大的尺寸
        self.page1.place_forget()
    
        self.page2_label = tk.Label(self.page2, text="", width=1000, height=200, bg='lightgreen')
        # 调整 page2 的大小和位置
        self.page2.place(x=0, y=0, width=1200, height=700)  # 调整为更大的尺寸
        
        self.page2.place(x=0, y=0, width=1200, height=700, in_=self.page1, anchor='nw', relwidth=0, relheight=0)  # Hidden initially
        self.page2.place_forget()

        self.tab_labels = [
            tk.Label(self, text="选择日志文件", bg='gray', fg='black', padx=15, pady=5, cursor="hand2"),
            tk.Label(self, text="显示分析结果", bg='gray', fg='black', padx=15, pady=5, cursor="hand2"),
        ]

        for i, label in enumerate(self.tab_labels):
            label.bind("<Button-1>", lambda e, page=i: self.switch_page(page))
            label.place(x=200 + i*200, y=30, anchor='e')

        self.current_page = tk.IntVar(value=0)
        self.browse_file_button = tk.Button(self.page1, text="选择文件", command=self.browse_file)
        self.browse_file_button.place(x=50, y=60)

        self.browse_dir_button = tk.Button(self.page1, text="选择文件夹", command=self.browse_directory)
        self.browse_dir_button.place(x=50, y=150)

        self.auto_file_button = tk.Button(self.page1, text="自动读取日志文件", command=self.check_and_get_recent_month_logs)
        self.auto_file_button.place(x=50, y=240)

       # 创建标签和输入框
        self.label_start_date = tk.Label(self.page1, text="开始日期 :")
        self.label_start_date.place(x=50, y=330)
 
        self.entry_start_date = tk.Entry(self.page1)
        self.entry_start_date.place(x=200, y=330, width=150)
        
        # 默认设置为当前日期的前一个月
        one_month_ago = datetime.datetime.now() - datetime.timedelta(days=1)
        self.entry_start_date.insert(0, one_month_ago.strftime('%Y-%m-%d'))
 
        self.label_end_date = tk.Label(self.page1, text="结束日期 :")
        self.label_end_date.place(x=50, y=420)
 
        self.entry_end_date = tk.Entry(self.page1)
        self.entry_end_date.place(x=200, y=420, width=150)
        self.entry_end_date.insert(0, datetime.datetime.now().strftime('%Y-%m-%d'))

        # 使用 Label 并绑定点击事件
        custom_font = font.Font(family='Arial', size=10, underline=True, weight='normal', slant='roman')
        label = tk.Label(self.page1, text="事件查看器", cursor="hand2")
        label.bind("<Button-1>", lambda event: self.open_event_viewer())
        label.place(x=700, y=600, anchor='e')
        
        # 使用 IntVar 跟踪 flag 变化
        self.flag_var = tk.IntVar(value=0)
        
        # 在 page2 布局中使用 trace 监听变化
        self.flag_var.trace_add("write", self.update_page2)
        # 创建带滚动条的文本显示框
        results_frame = ttk.Frame(self.page2)
        results_frame.place(x=0, y=60, width=1200, height=640)
        
        # 创建垂直滚动条
        scrollbar = ttk.Scrollbar(results_frame)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # 创建文本显示框
        self.result_text = tk.Text(
            results_frame,
            wrap=tk.WORD,
            yscrollcommand=scrollbar.set,
            font=('Consolas', 20),
            state='disabled'  # 初始设置为只读
        )
        self.result_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        # 关联滚动条
        scrollbar.config(command=self.result_text.yview)
        self.cancel_button = tk.Button(
            self.page1, 
            text="取消分析",
            command=self.cancel_analysis,
            state='disabled'
        )
        self.cancel_button.place(x=300, y=70)



    def update_page2(self, *args):
        # 自动触发页面更新
        if self.flag_var.get() != 0:
            self.analyze_logs()

    
    def is_evtx_file(self, file_path):
        """检查文件是否为指定的 .evtx 格式（System.evtx 或 Security.evtx）"""
        if file_path and os.path.isfile(file_path):
            file_name, file_extension = os.path.splitext(file_path)
            # 先检查扩展名是否为 .evtx
            if file_extension.lower() == '.evtx':
                # 再检查文件名是否为 System 或 Security（不带扩展名）
                base_file_name = os.path.basename(file_name)  # 使用 basename 以防文件路径中包含目录名
                if base_file_name.lower() in ['system', 'security']:
                    self.get_recent_month_logs(file_path)



    def browse_file(self):
        file_path = filedialog.askopenfilename()
        self.is_evtx_file(file_path)
        data_dir = Path(__file__).parent / "data" / "raw"  # ✅ Path对象
        self.flag_var = len(list(data_dir.iterdir()))  # 统计目录下条目数量



    def browse_directory(self):
        dir_path = filedialog.askdirectory()
        if dir_path:
            for root, dirs, files in os.walk(dir_path):
                for file in files:
                    file_path = os.path.join(root, file)
                    self.is_evtx_file(file_path)

            data_dir = Path(__file__).parent / "data" / "raw"  # ✅ Path对象
            self.flag_var = len(list(data_dir.iterdir()))  # 统计目录下条目数量



    def open_event_viewer(self):
        if os.name == 'nt':  # 检查是否为Windows系统
            os.startfile('eventvwr.msc')
        else:
            messagebox.showinfo("完成", "日志分析已完成")



    def cancel_analysis(self):
        self.is_processing = False
        if hasattr(self, '_evtx_progress'):
            self._evtx_progress.destroy()
            del self._evtx_progress
        self.title("日志分析系统")
        self.cancel_button.config(state='disabled')



    def check_and_get_recent_month_logs(self):
        # 检查/data/raw目录是否为空
        output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data", "raw")
        if os.listdir(output_dir):  # 如果目录不为空
            # 弹出对话框询问用户
            response = messagebox.askyesnocancel(
                "检测到已有文件",
                "检测到/data/raw目录下已有文件，是否用已有文件进行分析，还是新建覆盖分析？"
            )
            if response is None:  # 用户点击取消
                return
            elif response:  # 用户点击是（使用已有文件）
                self.analyze_logs()
            else:  # 用户点击否（新建覆盖分析）
                # 清空/data/raw目录
                for file in os.listdir(output_dir):
                    file_path = os.path.join(output_dir, file)  
                    if os.path.isfile(file_path):
                        os.remove(file_path)
                # 继续执行原有操作
                self.get_recent_month_logs()
        else:  # 目录为空，直接执行原有操作
            self.get_recent_month_logs()


 
    def get_recent_month_logs(self, file_path=None):
        try:
            # 获取用户输入的日期
            start_date_str = self.entry_start_date.get()
            end_date_str = self.entry_end_date.get()

            # 转换为 datetime 对象并验证
            self.start_date = datetime.datetime.strptime(start_date_str, '%Y-%m-%d')
            self.end_date = datetime.datetime.strptime(end_date_str, '%Y-%m-%d')
            if self.start_date > self.end_date:
                raise ValueError("开始日期不能晚于结束日期")

            # 获取 EvtxECmd 路径
            project_root = os.path.dirname(os.path.abspath(__file__))
            evtxecmd_path = os.path.join(project_root, "tools", "EvtxECmd", "EvtxECmd.exe")
            if not os.path.exists(evtxecmd_path):
                raise FileNotFoundError(f"EvtxECmd 未找到：{evtxecmd_path}")

            # 创建输出目录
            output_dir = os.path.join(project_root, "data", "raw")
            os.makedirs(output_dir, exist_ok=True)

            # 构建命令参数
            date_format = "%Y-%m-%d"
            base_cmd = [
                evtxecmd_path,
                "--sd", self.start_date.strftime(date_format),
                "--ed", self.end_date.strftime(date_format),
                "--json", output_dir
            ]

            # # 处理默认日志路径或自定义文件路径
            system_root = os.getenv('SystemRoot')
            system_log = os.path.join(system_root, 'System32', 'Winevt', 'Logs', 'System.evtx')
            security_log = os.path.join(system_root, 'System32', 'Winevt', 'Logs', 'Security.evtx')
                
            # 启动后台线程
            self.is_processing = True
            self.cancel_button.config(state='normal')
            threading.Thread(
                target=self._run_evtxecmd,
                args=(base_cmd, system_log, security_log, output_dir, file_path),
                daemon=True
            ).start()

        except ValueError as e:
            messagebox.showerror("错误", str(e))
        except subprocess.CalledProcessError as e:
            messagebox.showerror("命令执行失败", f"EvtxECmd 错误: {e.stderr}")
        except Exception as e:
            messagebox.showerror("未知错误", str(e))


    

    def _run_evtxecmd(self, base_cmd, system_log, security_log, output_dir, file_path):
        """EvtxECmd执行线程"""
        try:
            if file_path is None:
                self._update_progress(1, 2, "System Log")
                subprocess.run(
                    base_cmd + ["-f", system_log, "--jsonf", "SystemLog.json"],
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.DEVNULL,
                    check=True,
                    shell=True
                )
                
                self._update_progress(2, 2, "Security Log")
                subprocess.run(
                    base_cmd + ["-f", security_log, "--jsonf", "SecurityLog.json"],
                    stdout=subprocess.DEVNULL,
                    stderr=subprocess.DEVNULL,
                    check=True,
                    shell=True
                )
            else:
                # 自定义文件路径
                filename = Path(file_path).stem
                csv_name = f"{filename}Log.json"
                subprocess.run(
                    base_cmd + ["-f", file_path, "--jsonf", csv_name],
                    stdout=subprocess.DEVNULL,  # 静默标准输出
                    stderr=subprocess.DEVNULL,  # 静默错误输出
                    check=True,
                    shell=True
                )
                
            # 完成后更新文件计数
            data_dir = Path(__file__).parent / "data" / "raw"
            self.flag_var.set(len(list(data_dir.iterdir())))

        except Exception as e:
            self.after(0, messagebox.showerror, "后台错误", str(e))
        finally:
            self.is_processing = False
            self.after(0, lambda: self.cancel_button.config(state='disabled'))


    def _update_progress(self, current, total, filename):
        """线程安全进度更新"""
        self.after(0, self._do_update_progress, current, total, filename)



    def _do_update_progress(self, current, total, filename):
        """实际界面更新"""
        if not hasattr(self, '_evtx_progress'):
            self._evtx_progress = ttk.Progressbar(
                self.page1, 
                orient='horizontal',
                length=300,
                mode='determinate'
            )
            self._evtx_progress.place(x=200, y=200)
            
        progress = (current / total) * 100
        self._evtx_progress['value'] = progress
        self.title(f"日志解析中 ({current}/{total}) {filename}")



    def switch_page(self, page_index):
        # Hide the current page
        if self.current_page.get() == 0:
            self.page1.place_forget()
        elif self.current_page.get() == 1:
            self.page2.place_forget()
        # Show the new page
        if page_index == 0:
            self.page1.place(x=0, y=0, width=1200, height=700)
        elif page_index == 1:
            # Update the label on page2 with the selected file/directory path (if any)
            if hasattr(self, 'file_path') and self.file_path:
                self.page2_label.config(text=f"File Path: {self.file_path}")
            elif hasattr(self, 'dir_path') and self.dir_path:
                self.page2_label.config(text=f"Directory Path: {self.dir_path}")
            else:
                self.page2_label.config(text="")
            self.page2.place(x=0, y=0, width=1200, height=700)

        # Update the current page variable
        self.current_page.set(page_index)

        # Highlight the selected tab label
        for i, label in enumerate(self.tab_labels):
            if i == page_index:
                label.config(bg='blue', fg='white')
            else:
                label.config(bg='gray', fg='white')



    def analyze_logs(self):
        if self.is_processing:
            return
        self.cancel_button.config(state='normal')
        # 禁用操作按钮
        self.auto_file_button.config(state='disabled')
        self.browse_file_button.config(state='disabled')
        self.browse_dir_button.config(state='disabled')
        
        # 清空结果显示
        self.result_text.config(state='normal')
        self.result_text.delete(1.0, tk.END)
        self.result_text.config(state='disabled')
        
        # 创建进度条
        self.progress = ttk.Progressbar(
            self.page2, 
            orient='horizontal',
            length=300,
            mode='determinate'
        )
        self.progress.place(x=50, y=220)
        
        # 启动后台线程
        self.is_processing = True
        threading.Thread(
            target=self._async_analyze,
            daemon=True
        ).start()
        
        # 启动队列处理器
        self.after(100, self._process_queue)



    def _async_analyze(self):
        """后台分析线程"""
        project_root = os.path.dirname(os.path.abspath(__file__))
        raw_dir = os.path.join(project_root, "data", "raw")
        
        processor = LogPreprocessor()
        analyzer = LogAnalyzer()
        
        all_files = [os.path.join(root, f) 
                    for root, _, files in os.walk(raw_dir) 
                    for f in files]
        
        total_files = len(all_files)
        
        for idx, file_path in enumerate(all_files, 1):
            if not self.is_processing:  # 允许取消
                break
                
            try:
                # 处理文件
                processed_df = processor.process(file_path)
                analysis_results = analyzer.analyze(processed_df)
                report = analyzer.generate_report(analysis_results, format='text')
                
                # 将结果放入队列
                self.task_queue.put((
                    'progress',
                    (idx, total_files, os.path.basename(file_path))
                ))
                self.task_queue.put(('report', report))
            except Exception as e:
                self.task_queue.put(('error', str(e)))
        self.task_queue.put(('finish', None))



    def _process_queue(self):
        """处理任务队列（在主线程执行）"""
        while not self.task_queue.empty():
            task_type, data = self.task_queue.get()
            
            if task_type == 'progress':
                idx, total, filename = data
                self._append_report(f"\n-- {filename} 分析完毕 --")
                
            elif task_type == 'report':
                self._append_report(data)
            elif task_type == 'error':
                messagebox.showerror("处理错误", data)
            elif task_type == 'finish':
                self._analysis_finished()
                return
        self.after(100, self._process_queue)



    def _append_report(self, content):
        """安全追加分析结果"""
        self.result_text.config(state='normal')
        self.result_text.insert(tk.END, content + '\n\n')
        self.result_text.see(tk.END)
        self.result_text.config(state='disabled')
        
        # 限制更新频率
        if len(content) > 1000:
            self.result_text.update_idletasks()



    def _analysis_finished(self):
        """分析完成处理"""
        self.is_processing = False
        self.progress.destroy()
        self.title("日志分析系统")
        
        # 恢复按钮状态
        self.auto_file_button.config(state='normal')
        self.browse_file_button.config(state='normal')
        self.browse_dir_button.config(state='normal')
        
        messagebox.showinfo("完成", "日志分析已完成")
if __name__ == "__main__":
    app = App()
    app.mainloop()  
    
    
